package net.fdukedom.epicurus.messaging;

import net.fdukedom.epicurus.domain.entity.MessageType;
import net.fdukedom.epicurus.tools.logging.Logger;
import net.fdukedom.epicurus.tools.logging.WriterId;

import java.util.*;

/**
 * Singleton that provides messaging features.
 *
 * @author Dmitry Y. Kochelaev
 */
public final class MessageDispatcher {

    /**
     * Instance of class.
     */
    private static final MessageDispatcher instance = new MessageDispatcher();

    private Logger logger = Logger.getInstance(WriterId.MESSAGING);

    /**
     * Utility class.
     */
    private MessageDispatcher() {
    }

    /**
     * Returns instance of the class.
     *
     * @return instance of the class
     */
    public static MessageDispatcher getInstance() {
        return instance;
    }

    /**
     * Map of lists of recievers for message's types.
     */
    private final Map<String, List<MessageRecipient>> tracesMap = new HashMap<String, List<MessageRecipient>>();

    /**
     * Map of listeners: &lt;listenerName, recipient&gt;.
     */
    private final Map<String, MessageRecipient> directRecievers = new HashMap<String, MessageRecipient>();

    /**
     * Adds recipient for provided broadcast messages' types.
     *
     * @param messageType message type
     * @param recipient recipient
     */
    synchronized private void addBroadcastReciever(MessageType messageType, MessageRecipient recipient) {
        List<MessageRecipient> recievers;
        if (tracesMap.containsKey(messageType.getName())) {
            recievers = tracesMap.get(messageType.getName());
        } else {
            recievers = new ArrayList<MessageRecipient>();
            tracesMap.put(messageType.getName(), recievers);
        }
        recievers.add(recipient);
    }

    /**
     * Registers recipient for non-broadcast messages.
     *
     * @param addressee recipient's direct name
     * @param recipient recipient itself
     */
    synchronized private void addDirectReciever(String addressee, MessageRecipient recipient) {
        directRecievers.put(addressee, recipient);
    }

    /**
     * Registers new recipient for specified messages' types.
     *
     * @param messagesTypes types of broadcast messages to register.
     * @param addressee name of recipient
     * @param recipient recipient to be registred.
     */
    public void registerReciever(Set<MessageType> messagesTypes, String addressee, MessageRecipient recipient) {
        for (MessageType type : messagesTypes) {
            addBroadcastReciever(type, recipient);
        }
        addDirectReciever(addressee, recipient);
        logger.logInfo("Recipient registered. Addressee:" + addressee + ". Receiver: " + recipient);
    }

    /**
     * Sends message broadcast.
     *
     * @param message message to be sent
     */
    public void send(Message message) {
        List<MessageRecipient> recipients = tracesMap.get(message.getType());
        if (recipients == null) {
            return;
        }
        for (ListIterator<MessageRecipient> i = recipients.listIterator();
                i.hasNext(); ) {
            i.next().recieve(message);
        }
    }

    /**
     * Sends message directly to addressee.
     * @param message message to be sent
     * @param addressee addressee for the message
     *
     * @throws AddresseeNotFoundException if addressee is not registred
     */
    public void send(Message message, String addressee) throws AddresseeNotFoundException {
        if (directRecievers.containsKey(addressee)) {
            directRecievers.get(addressee).recieve(message);
        } else {
            logger.logWarning("Failed to send message to " + addressee);
            throw new AddresseeNotFoundException(addressee);
        }
    }
}
